/*
	* Created by : Varun Tyagi
	*
	* This is a simple tutorial that'll explain the working of particle engine in a game. 
*/

/* Header files. */
#include "SDL.h"
#include "SDL_image.h"
#include <string>
#include <cstdlib>

/* The surfaces. */
const int SCREEN_WIDTH	= 640;
const int SCREEN_HEIGHT	= 480;
const int SCREEN_BPP	= 32;

/* Frame Rate. */
const int FRAMES_PER_SECOND	= 20;

/* Dot dimensions. */
const int DOT_WIDTH		= 20;
const int DOT_HEIGHT	= 20;

/* Total particles. */
const int TOTAL_PARTICLES	= 20;

/* Surfaces. */
SDL_Surface *pDot		= nullptr;
SDL_Surface *pRed		= nullptr;
SDL_Surface *pGreen		= nullptr;
SDL_Surface *pBlue		= nullptr;
SDL_Surface *pShimmer	= nullptr;
SDL_Surface *pScreen	= nullptr;

/* The event structure. */
SDL_Event event;

/* Particle class. */
class Particle
{
private:

	/* Offsets. */
	int m_iX;
	
	int m_iY;

	/* Current frame of animation. */
	int m_iFrame;

	/* Type of particle. */
	SDL_Surface *m_pType;

public:

	/* Initialize. */
	Particle( int iX, int iY );

	/* Show the particle. */
	void show_particle( void );

	/* Check to see if the particle is dead. */
	bool is_dead( void );
};

/* DOT class. */
class Dot
{
private:

	/* Offset. */
	int m_iX;

	int m_iY;

	/* Velocity of the dot. */
	int m_iVelX, m_iVelY;

	/* The particles. */
	Particle *m_pParticle[ TOTAL_PARTICLES ];

public:

	/* Initialize. */
	Dot();

	/* Clean the particles. */
	~Dot();

	/* Input handler. */
	void handle_input( void );

	/* Move the dot. */
	void move_dot( void );

	/* Show the particles. */
	void show_particles( void );

	/* Show the dot. */
	void show_dot( void );
};

/* Timer class. */
class Timer
{
private:

	/* The tick when the timer was started. */
	int m_iStartTick;

	/* The tick when the timer was paused. */
	int m_iPauseTick;

	/* Status of the timer. */
	bool m_bPause;
	bool m_bStart;

public:

	/* Initialize. */
	Timer();

	/* Timer functions. */
	void start( void );
	void stop( void );
	void pause( void );
	void unpause( void );

	/* Get the current time. */
	int get_tick( void );

	/* Get the status. */
	bool has_started( void );
	bool is_paused( void );
};

/* Load the image. */
SDL_Surface *load_image( std::string filename )
{
	/* The loaded image. */
	SDL_Surface *pLoadedImage = nullptr;

	/* The optimized image. */
	SDL_Surface *pOptimizedImage = nullptr;

	/* Load the image. */
	pLoadedImage = IMG_Load( filename.c_str() );

	/* Check the loaded image. */
	if( nullptr != pLoadedImage )
	{
		/* Optimize the image. */
		pOptimizedImage = SDL_DisplayFormat( pLoadedImage );

		/* Check the optimized image. */
		if( nullptr != pOptimizedImage )
		{
			/* Color key surface. */
			SDL_SetColorKey( pOptimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( pOptimizedImage->format, 0xFF, 0xFF, 0xFF ) );
		}
	}

	/* Return the optimized image. */
	return pOptimizedImage;
}

/* Load surface. */
void apply_surface( int x, int y, SDL_Surface *pSouce, SDL_Surface *pDestination, SDL_Rect* pRects = nullptr )
{
	/* Offset. */
	SDL_Rect offset;

	/* Get the offset. */
	offset.x = x;
	offset.y = y;

	/* BLit the surface. */
	SDL_BlitSurface( pSouce, pRects, pDestination, &offset );
}

/* Init SDL. */
bool init_SDL( void )
{
	/* Init everything. */
	if( -1 == SDL_Init( SDL_INIT_EVERYTHING ) )
	{
		return false;
	}

	/* Set up the screen. */
	pScreen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );

	/* Check the screen. */
	if( nullptr == pScreen )
	{
		return false;
	}

	/* Set the caption. */
	SDL_WM_SetCaption( "Particle Engine.", nullptr );

	/* Seed Random. */
	srand( SDL_GetTicks() );

	/* If everything went well, return 1. */
	return true;
}

/* Load the files. */
bool load_files( void )
{
	/* Load the entity. */
	pDot = load_image( "dot.bmp" );

	/* Check the loaded file. */
	if( nullptr == pDot )
	{
		return false;
	}

	/* Load the particles. */
	pRed	= load_image( "red.bmp" );
	pGreen	= load_image( "green.bmp" );
	pBlue	= load_image( "blue.bmp" );
	pShimmer= load_image( "shimmer.bmp" );

	/* Check to see if all the files are loaded properly. */
	if( ( nullptr == pShimmer ) || ( nullptr == pBlue ) || ( nullptr == pGreen ) || ( nullptr == pRed ) )
	{
		return false;
	}

	/* Set alpha. */
	SDL_SetAlpha( pRed, SDL_SRCALPHA | SDL_RLEACCEL, 192 );
	SDL_SetAlpha( pGreen, SDL_SRCALPHA | SDL_RLEACCEL, 192 );
	SDL_SetAlpha( pBlue, SDL_SRCALPHA | SDL_RLEACCEL, 192 );
	SDL_SetAlpha( pShimmer, SDL_SRCALPHA | SDL_RLEACCEL, 192 );

	/* Return if everything went well. */
	return true;
}

/* Clean up. */
void clean_SDL( void )
{
	/* Free surface. */
	SDL_FreeSurface( pDot );
	SDL_FreeSurface( pRed );
	SDL_FreeSurface( pGreen );
	SDL_FreeSurface( pBlue );
	SDL_FreeSurface( pShimmer );

	/* QUIT SDL. */
	SDL_Quit();
}

/*********** PARTICLE ***************/

Particle::Particle( int iX, int iY )
{
	/* Set the offsets. */
	m_iX = iX - 5 + ( rand() % 25 );
	m_iY = iY - 5 + ( rand() % 25 );

	/* Initialize animation. */
	m_iFrame = rand() % 5;

	/* Set type. */
	switch( rand() % 3 )
	{
	case 0:
		{
			m_pType = pRed;
			break;
		}
	case 1:
		{
			m_pType = pBlue;
			break;
		}
	case 2:
		{
			m_pType = pGreen;
			break;
		}
	}
}


void Particle::show_particle( void )
{
	/* Show the image. */
	apply_surface( m_iX, m_iY, m_pType, pScreen );

	/* Show shimmer. */
	if( m_iFrame % 2 == 0 )
	{
		apply_surface( m_iX, m_iY, pShimmer, pScreen );
	}

	/* Animate. */
	m_iFrame++;
}

bool Particle::is_dead( void )
{
	if( 10 < m_iFrame )
	{
		/* Kill the particle. */
		return true;
	}

	return false;
}


/*********** DOT ***************/
Dot::Dot()
{
	/* Initialize. */
	m_iX = m_iY = 0;

	/* Set the velocity. */
	m_iVelX = m_iVelY = 0;

	/* Initialize the particle. */
	for( int p = 0; p < TOTAL_PARTICLES; ++p )
	{
		m_pParticle[ p ] = new Particle( m_iX, m_iY );
	}
}

Dot::~Dot()
{
	/* Kill the particles. */
	for( int p = 0; p < TOTAL_PARTICLES; ++p )
	{
		delete m_pParticle[ p ];
	}
}

void Dot::handle_input( void )
{
	/* If a key is pressed down. */
	if( SDL_KEYDOWN == event.type )
	{
		/* Adjust the velocity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_KP8:
			{
				m_iVelY -= DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP2:
			{
				m_iVelY += DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP4:
			{
				m_iVelX -= DOT_WIDTH / 2;
				break;
			}
		case SDLK_KP6:
			{
				m_iVelX += DOT_WIDTH / 2;
				break;
			}
		}
	}
	
	/* If the key is released. */
	else if( SDL_KEYUP == event.type )
	{
		/* Adjust the velocity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_KP8:
			{
				m_iVelY -= DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP2:
			{
				m_iVelY += DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP4:
			{
				m_iVelX -= DOT_WIDTH / 2;
				break;
			}
		case SDLK_KP6:
			{
				m_iVelX += DOT_WIDTH / 2;
				break;
			}
		}
	}
}

void Dot::move_dot( void )
{
	/* Move the dot left or right. */
	m_iX += m_iVelX;

	/* Check if the dot is going out of the screen. */
	if( ( 0 > m_iX ) || ( m_iX + DOT_WIDTH > SCREEN_WIDTH ) )
	{
		/* Move back. */
		m_iX -= m_iVelX;
	}

	/* Move dot up or down. */
	m_iY += m_iVelY;

	/* Check if the dot is going out of the screen. */
	if( ( 0 > m_iY ) || ( m_iY + DOT_HEIGHT > SCREEN_HEIGHT ) )
	{
		/* Move back. */
		m_iY -= m_iVelY;
	}
}

void Dot::show_particles( void )
{
	/* Go through the particles. */
	for( int p = 0; p < TOTAL_PARTICLES; ++p )
	{
		/* Delete and replace the dead particles. */
		if( true == m_pParticle[ p ]->is_dead() )
		{
			delete m_pParticle[ p ];

			m_pParticle[ p ] =  new Particle( m_iX, m_iY );
		}
	}

	/* Show particles. */
	for( int p = 0; p < TOTAL_PARTICLES; ++p )
	{
		m_pParticle[ p ]->show_particle();
	}
}

void Dot::show_dot( void )
{
	/* Show the dot. */
	apply_surface( m_iX, m_iY, pDot, pScreen );

	/* Show the particle. */
	show_particles();
}

/*********** TIMER ***************/
Timer::Timer()
{
	/* Init. */
	m_iStartTick = m_iPauseTick = 0;

	m_bStart = m_bPause = false;
}

void Timer::start( void )
{
	/* Start the timer. */
	m_bStart = true;

	/* Unpause the timer. */
	m_bPause = false;

	/* Get the tick when the timer was started. */
	m_iStartTick = SDL_GetTicks();
}

void Timer::stop( void )
{
	/* Stop the timer. */
	m_bStart = false;

	/* Unpause. */
	m_bPause = false;
}

void Timer::pause( void )
{
	/* Check if the timer is running and is not paused. */
	if( ( true == m_bStart ) && ( false == m_bPause ) )
	{
		/* pause the timer. */
		m_bPause = true;

		/* Get the tick when the timer was paused. */
		m_iPauseTick = SDL_GetTicks() - m_iStartTick;
	}
}

void Timer::unpause( void )
{
	/* If the timer is paused. */
	if( true == m_bPause )
	{
		/* Unpause. */
		m_bPause = false;

		/* Reset the starting tick. */
		m_iStartTick = SDL_GetTicks() - m_iPauseTick;

		/* Reset the pause tick. */
		m_iPauseTick = 0;
	}
}

int Timer::get_tick( void )
{
	/* If the timer is running. */
	if( true == m_bStart )
	{
		/* If the timer is paused. */
		if( true == m_bPause )
		{
			/* Return pause time. */
			return m_iPauseTick;
		}
		else
		{
			return SDL_GetTicks() - m_iStartTick;
		}
	}

	return 0;
}

bool Timer::has_started( void )
{
	return m_bStart;
}

bool Timer::is_paused( void )
{
	return m_bPause;
}

/* Main. */
int main( int argc, char** argv )
{
	/* QUIT flag. */
	bool bQuit = false;

	/* The dot in the game. */
	Dot myDot;

	/* Frame rate regulator. */
	Timer fps;

	/* Initialize. */
	if( false == init_SDL() )
	{
		return 1;
	}

	/* Load the files. */
	if( false == load_files() )
	{
		return 1;
	}

	/* While the user hasn't quit. */
	while( false == bQuit )
	{
		/* Start the frame timer. */
		fps.start();

		/* While there is event to handle. */
		while( SDL_PollEvent( &event ) )
		{
			/* Handle inputs for dot. */
			myDot.handle_input();

			/* If the user has Xed out the window. */
			if( SDL_QUIT == event.type )
			{
				bQuit = true;
			}
		}

		/* Move the dot. */
		myDot.move_dot();

		/* Fill the screen white. */
		SDL_FillRect( pScreen, &pScreen->clip_rect, SDL_MapRGB( pScreen->format, 0xFF, 0xFF, 0xFF ) );

		/* Show the dot on the screen. */
		myDot.show_dot();

		/* Update the screen. */
		if( -1 == SDL_Flip( pScreen ) )
		{
			return 1;
		}

		/* Cap the frame rate. */
		if( fps.get_tick() < 1000 / FRAMES_PER_SECOND )
		{
			SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_tick() );
		}
	}

	/* Clean up. */
	clean_SDL();

	return 0;

}